package ca.concordia.pga.main;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;

import ca.concordia.pga.models.Concept;
import ca.concordia.pga.models.PlanningGraph;
import ca.concordia.pga.models.Service;

public class SolutionBuilder {

	private Set<Concept> usedConcepts = new HashSet<Concept>();
	private Set<Service> usedServices = new HashSet<Service>();
	private PlanningGraph pg;
	private long startTime, elapsedTime;
	private ArrayList<Set<Service>> solution = new ArrayList<Set<Service>>();

	public SolutionBuilder(PlanningGraph pg) {
		this.pg = pg;
		for (int i=0;i<pg.getALevels().size()-1;++i)
			solution.add(new HashSet<Service>());
		getUsedConcepts();
	}

	public void getUsedConcepts() {
		usedConcepts.addAll(pg.getGoalSet());
		for (Concept c : pg.getGoalSet()) {
			for (Service s : c.getOriginServiceSet()) {
				for (Concept con : s.getInputConceptSet())
					if (!pg.getOriginalConceptSet().contains(con))
						usedConcepts.add(con);
			}
		}
	}

	public void getUsedServices() {
		for (Concept c : pg.getGoalSet()) {
			for (Service s : c.getOriginServiceSet()) {
				s.setFinalService(true);
				usedServices.add(s);
			}
		}
	}

	public static Set<Concept> getIntersection(Set<Concept> set1, Set<Concept> set2) {
		boolean set1IsLarger = set1.size() > set2.size();
		Set<Concept> cloneSet = new HashSet<Concept>(set1IsLarger ? set2 : set1);
		cloneSet.retainAll(set1IsLarger ? set1 : set2);
		return cloneSet;
	}

	//checks to see if output of one service is subset of another
	//if so, eliminates the subset service from the final solution
	public void pruneServices() {
		for (Service s : usedServices) {
			System.out.println("Service: "+s.getName()+", "+s.isFinalService());
			if (s.isFinalService()) {
				Set<Concept> outsideSet = getIntersection(s.getOutputConceptSet(), usedConcepts);
				for (Service p : usedServices) {
					if (!s.equals(p)&&s.isFinalService()) {
						Set<Concept> insideSet = getIntersection(p.getOutputConceptSet(), usedConcepts);
						if (outsideSet.containsAll(insideSet)) {
							p.setFinalService(false);
						}
					}
				}
			}
		}
	}

	//checks to see if output of one service is subset of any combination of two others
	//if so, eliminates the subset service from the final solution
	public void pruneServices2() {
		for (Service q : usedServices) {
			for (Service s : usedServices) {
				if (!q.equals(s)) {					
					if (s.isFinalService()&&q.isFinalService()) {
						Set<Concept> outsideSet = new HashSet<Concept>();
						outsideSet.addAll(q.getOutputConceptSet());
						outsideSet.addAll(s.getOutputConceptSet());
						outsideSet = getIntersection(outsideSet, usedConcepts);
						for (Service p : usedServices) {
							if (!s.equals(p)&&!q.equals(p)) {
								Set<Concept> insideSet = getIntersection(p.getOutputConceptSet(), usedConcepts);
								if (outsideSet.containsAll(insideSet)&&!p.isConcreteSolution()) {
									q.setConcreteSolution(true);
									s.setConcreteSolution(true);
									p.setFinalService(false);
								}
							}
						}
					}
				}
			}
		}
	}

	public void findSolutions() {
		startTime = System.currentTimeMillis();
		getUsedServices();
		pruneServices2();
		for (Service s : usedServices) {
			if (s.isFinalService())
				solution.get(findLevel(s)-1).add(s);
		}
		elapsedTime = System.currentTimeMillis()-startTime;
		printSolution();
		outputSolution();
	}

	public void outputSolution() {
		Output o = new Output();
		o.outputResults(solution);
	}

	public void printSolution() {
		System.out.println("\n\n************ This solution graph is shown as follows**************");
		System.out.print("Input concepts: ");
		for (Concept c : pg.getOriginalConceptSet())
			System.out.print(c.getName()+", ");
		System.out.print("\nGoal concepts: ");
		for (Concept c : pg.getGoalSet())
			System.out.print(c.getName()+", ");
		int invoked=0;
		for (int i=0;i<solution.size();++i) {
			invoked+=solution.get(i).size();
			System.out.println("\n\n************Level "+(i+1)+"*****************");
			System.out.print("Services: ");
			for (Service s : solution.get(i)) {
				System.out.println("\n"+s.getName()+", ");
				System.out.println("INPUT");
				for (Concept c : s.getInputConceptSet())
					System.out.print(c.getName()+", ");
				System.out.println("\nOUTPUT");
				for (Concept c : s.getOutputConceptSet())
					System.out.print(c.getName()+", ");
			}
			System.out.print("\nNumber of services: "+solution.get(i).size()); 
		}
		System.out.println("\n\n================================");
		System.out.println("Solution Building Time: "+elapsedTime+"ms");
		System.out.println("Invoked service: "+invoked);
		System.out.println("Levels: "+(pg.getALevels().size()-1));
		System.out.print("==================================");
	}

	public int findLevel(Service s) {
		for (int i=0;i<pg.getALevels().size();i++)
			if (pg.getALevels().get(i).contains(s))
				return i;
		return -1;
	}
}
